﻿/* ----------------------------------------------------------------------------
 * Copyright (c) Philip Stears 
 *
 * This source code is subject to terms and conditions of the Microsoft Public 
 * License (Ms-PL).
 * 
 * By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 * 
 * To request a copy of the Microsoft Public License, send a request by email
 * to license@connectbasic.net.
 *
 * This notice must not be removed from this software.
 * ---------------------------------------------------------------------------- */

namespace ConnectBasic
{
    using System;
    using System.Collections.Generic;
    using ConnectBasic.Execution;
    using Microsoft.Linq.Expressions;
    using Microsoft.Scripting;
    using Microsoft.Scripting.Hosting;
    using Microsoft.Scripting.Runtime;

    /// <summary>
    /// Provides the primary DLR integration for Connect Basic.
    /// </summary>
    public class BasicLanguageContext : LanguageContext
    {

        /// <summary>
        /// The name of the language.
        /// </summary>
        public const string LanguageName = "ConnectBasic";

        /// <summary>
        /// The file extensions supported by the language.
        /// </summary>
        public const string LanguageExtensions = "bas";

        /// <summary>
        /// The unique identifier of the language vendor.
        /// </summary>
        private static readonly Guid LanguageVendorId = new Guid("{B57C40F4-9265-4467-B44D-2287B8A22D84}");

        /// <summary>
        /// The unique identifier of the language.
        /// </summary>
        private static readonly Guid LanguageId = new Guid("{DC9AE2A2-646A-4452-A7BB-D2CFC35FA460}");

        /// <summary>
        /// Controls whether debugging support is enabled.
        /// </summary>
        private bool mEmitDebugProxy;

        #region .ctor

        public BasicLanguageContext(ScriptDomainManager domainManager, IDictionary<string, object> options)
            : base(domainManager)
        {
            object result;

            if (options.TryGetValue("EmitExpressionDebugger", out result) && result is bool && (bool)result)
            {
                mEmitDebugProxy = true;
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Determines whether debugging support is enabled.
        /// </summary>
        public bool IsDebuggingEnabled
        {
            get { return mEmitDebugProxy; }
        }

        #endregion

        #region Overridden Behavior

        /// <inheritdoc />
        public override Guid LanguageGuid
        {
            get
            {
                return LanguageId;
            }
        }

        /// <inheritdoc />
        public override Guid VendorGuid
        {
            get
            {
                return LanguageVendorId;
            }
        }

        /// <inheritdoc />
        protected override ScriptCode CompileSourceCode(SourceUnit sourceUnit, CompilerOptions options, ErrorSink errorSink)
        {
            if (sourceUnit == null) throw new ArgumentOutOfRangeException("sourceUnit");
            if (errorSink == null) errorSink = ErrorSink.Null;

            var expression = ParseSource(sourceUnit, options, errorSink);

            if (expression == null)
            {
                return null;
            }
            else
            {
                return new LegacyScriptCode(expression, sourceUnit);
            }
        }

        public override TService GetService<TService>(params object[] args)
        {
            if (typeof(TService) == typeof(TokenizerService))
            {
                return (TService)(object)new BasicTokenizerService();
            }

            return base.GetService<TService>(args);
        }

        #endregion

        #region Parsing

        private LambdaExpression ParseSource(SourceUnit sourceUnit, CompilerOptions options, ErrorSink errorSink)
        {
            if (sourceUnit == null) throw new ArgumentNullException("sourceUnit");
            
            // Parse the source
            var result = BasicParser.ParseStandardModule(sourceUnit, errorSink, null);
       
            // Generate it
            var scope = Expression.Parameter(typeof(Scope), "#scope");
            var context = Expression.Parameter(typeof(LanguageContext), "#context");
            
            var module = BasicCodeGenerator.GenerateModule(this, result);

            return Expression.Lambda<DlrMainCallTarget>(module, scope, context);
        }

        #endregion

        #region Public Static Helpers

        /// <summary>
        /// Creates a fully initialized instance of the <see cref="LanguageSetup" /> class
        /// for use with ConnectBasic.
        /// </summary>
        /// <returns></returns>
        public static LanguageSetup CreateLanguageSetup()
        {
            return new LanguageSetup(
                typeof(BasicLanguageContext).AssemblyQualifiedName,
                string.Empty,
                LanguageName.Split(';'),
                LanguageExtensions.Split(';'));
        }

        #endregion

    }
}
